package com.mjk.common.core.jdbc.ibatis.interceptor;


import com.mjk.common.core.jdbc.annotation.DataBaseId;
import com.mjk.common.core.jdbc.datasource.DynamicDataSource;
import com.mjk.common.tools.logger.BizLogger;
import com.mjk.common.tools.springextend.BeeApplicationListener;
import com.mjk.common.util.BeanUtils;
import com.mjk.common.util.ClassUtil;
import com.mjk.common.util.ReflectionUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Method;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

//@Intercepts({
//	@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
//    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
//})
public class MasterSlaveInterceptor implements Interceptor, BeeApplicationListener {
	@Value("${sharding.jdbc.datasource.readename:readhis}")
	private String READ_BASESOURCE_NAME;
	@Value("${sharding.jdbc.datasource.defaultname:his}")
	private String DEFAULT_DATESOURCE_NAME;
	/**
	 * 是否开启读写分离转化
	 */
	@Value("${sharding.jdbc.masterslave:true}")
	private String masterSlave;
	/**
     * 判断是插入还是增加还是删除之类的正则, u0020是空格
     */
    private static final String regex = ".*insert\\u0020.*|.*delete\\u0020.*|.update\\u0020.*";
    /**
     * 判断序列
     */
    private static final String seqRegex = "select .*.nextval.* from dual";
    /**
     * 存储过程
     */
    private static final String callStatmentRegex = ".*call.*";    
    /**
     * 这个map用于存放已经执行过的sql语句所对应的数据源
     */
    private static final Map<String, String> cacheMap = new ConcurrentHashMap<>();
	public synchronized Object intercept(Invocation invocation) throws Throwable {
		//获取当前事务同步性进行判断
        boolean synchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();
        //如果当前正在使用事务，则使用写库
        if (synchronizationActive) {        	
        	return invocation.proceed();        	
        }
		
        //从代理类参数中获取参数
        Object[] objects = invocation.getArgs();
        //其中参数的第一个值为执行的sql语句
        MappedStatement ms = (MappedStatement) objects[0];
                
        //当前sql语句所应该使用的数据源，通过sql语句的id从map中获取，如果获取到，则之前已经执行过直接取，
        String typeName = cacheMap.get(ms.getId());
        
        if (!ObjectUtils.isEmpty(typeName)) {
        	DynamicDataSource.setDatabaseType(typeName);
            return invocation.proceed();
        }
        if("false".equals(masterSlave)){
			return invocation.proceed();
		}
        //如果没有，则重新进行存放
        //ms中获取方法，如果是查询方法
        if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {           
        	//如果selectKey 为自增id查询主键（select last_insert_id（）方法），使用主库，这个查询是自增主键的一个查询
            if (ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)){
            	//主库
            	typeName = DEFAULT_DATESOURCE_NAME;
            }else{
            	//获取到绑定的sql
                BoundSql boundSql = ms.getSqlSource().getBoundSql(objects[1]);
                String sqlstr = boundSql.getSql();
                //toLowerCase方法用于把字符串转换为小写,replaceAll正则将所有的制表符转换为空格
                String sql = sqlstr.toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]", " ");
                //sql是这个===================：select top 1 * from cipentinfo where regno=?               
                //使用sql去匹配正则，看他是否是增加、删除、修改的sql，如果是则使用主库
                if (sql.matches(regex)){
                	typeName = DEFAULT_DATESOURCE_NAME;
                }else if(sql.matches(seqRegex)){//获取序列指定到主库
                	typeName = DEFAULT_DATESOURCE_NAME;
                } else if(sql.matches(callStatmentRegex)){//调用存储过程
                	typeName = DEFAULT_DATESOURCE_NAME;
                } else {
                    //从读库（从库），注意，读写分离后一定不能将数据写到读库中，会造成非常麻烦的问题
                	typeName = READ_BASESOURCE_NAME;
                }
            }        	                        
        }else{
        	typeName = DEFAULT_DATESOURCE_NAME;   
        }
        //将sql对应使用的数据源放进map中存放
        if(!ObjectUtils.isEmpty(typeName)){
	        cacheMap.put(ms.getId(), typeName);	        
	        //最后设置使用的数据源	        
	        DynamicDataSource.setDatabaseType(typeName);
        }
        //执行代理之后的方法
        return invocation.proceed();
	}
	@Override
	public Object plugin(Object target) {
	   if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
	}
	@Override
	public void setProperties(Properties properties) {
		//do noting
	}
	
	@Override
	public void doApplicationEvent(ContextRefreshedEvent event) {
		Map<String,Object> mappers = event.getApplicationContext().getBeansWithAnnotation(org.apache.ibatis.annotations.Mapper.class);
		for(Entry<String,Object> entry : mappers.entrySet()){
			Object o = ReflectionUtils.getFieldValue(entry.getValue(), "h");
			if(ObjectUtils.isEmpty(ReflectionUtils.getField(o.getClass(), "mapperInterface"))){
				BizLogger.warn(entry.getKey());
				continue;
			}
			Object mappeInterface =ReflectionUtils.getFieldValue(o, "mapperInterface");
			Object className =ReflectionUtils.getFieldValue(mappeInterface, "name");	
			Method[] methds = ClassUtil.loadClass(className.toString()).getDeclaredMethods();
			for(Method m : methds){
				DataBaseId database = m.getAnnotation(DataBaseId.class);
				if(database!=null){
					cacheMap.put(className+"."+m.getName(), database.name());
				}
			}
		}	
	}
}
